package http.secure.common.filter;

import http.secure.common.anno.RequestEnc;
import http.secure.common.constant.RequestTemplate;
import http.secure.common.constant.SecureConstant;
import http.secure.common.constant.SecureProperties;
import http.secure.common.event.RequestRejectHandler;
import http.secure.common.event.entity.AuditEvent;
import http.secure.common.exception.ExpiredRequestException;
import http.secure.common.exception.InvalidRequestException;
import http.secure.common.exception.RepeatedRequestException;
import http.secure.common.keys.SecureGuidSession;
import http.secure.common.keys.SecureKeyStore;
import http.secure.common.keys.validator.RepeatValidator;
import http.secure.common.util.Assert;
import http.secure.common.util.MappingUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.http.meta.HeaderName;
import org.dromara.hutool.http.server.servlet.ServletUtil;
import org.dromara.hutool.http.useragent.UserAgent;
import org.dromara.hutool.http.useragent.UserAgentParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;

import java.io.IOException;
import java.util.Objects;

@Slf4j
@Component
@RequiredArgsConstructor
public final class SecureRequestFilter {
    private final ApplicationEventPublisher publisher;
    private final SecureProperties properties;
    private final RepeatValidator validator;
    private final SecureKeyStore keyStore;
    private final SecureGuidSession guidSession;
    @Autowired(required = false)
    private RequestRejectHandler rejectHandler;

    /**
     * 解密并验证请求
     */
    public void filter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest _request = (HttpServletRequest) request;
        // 获取执行此 URI 的处理程序
        HandlerMethod handler = MappingUtil.getHandlerMethod(_request);
        if (handler == null) {
            chain.doFilter(request, response);
            return;
        }
        // 获取标记的RequestEnc注解
        RequestEnc anno = handler.getBeanType().getAnnotation(RequestEnc.class);
        if (anno == null) {
            anno = handler.getMethod().getAnnotation(RequestEnc.class);
        }
        // 执行解密
        if (anno != null) {
            try {
                String key = keyStore.get(SecureConstant.SM4_KEY);
                String iv = keyStore.get(SecureConstant.BROWSER_IV);
                Assert.notEmpty(key, "key is null", InvalidRequestException::new);
                Assert.notEmpty(iv, "iv is null", InvalidRequestException::new);
                // 校验IP一致性
                if (properties.isVerifyRequestRemote()) {
                    boolean equals = Objects.equals(keyStore.get(SecureConstant.IP_ADDR), ServletUtil.getClientIP(_request));
                    Assert.isTrue(equals, RequestTemplate.IP_ADDRESS_MISMATCH, InvalidRequestException::new);
                }
                SecureRequestWrapper wrapper = new SecureRequestWrapper(_request);
                // 解密请求
                wrapper.decrypt(key, iv);
                // 过期请求验证
                long time = wrapper.getTimestamp();
                boolean expired = validator.isExpired(time, properties.getLinkValidTime());
                Assert.isFalse(expired, () -> new ExpiredRequestException(RequestTemplate.EXPIRED_REQUEST, time, System.currentTimeMillis()));
                // 重复请求验证
                boolean repeated = validator.isRepeated(time, wrapper.sign());
                Assert.isFalse(repeated, () -> new RepeatedRequestException(RequestTemplate.REPEATED_REQUEST, time, System.currentTimeMillis()));
                // 签名验证
                boolean signature = wrapper.compareSign();
                Assert.isTrue(signature, () -> new InvalidRequestException(RequestTemplate.INVALID_REQUEST_SIGNATURE, wrapper.sign()));
                request = wrapper;
            } catch (Exception e) {
                HttpServletResponse _response = (HttpServletResponse) response;
                if (rejectHandler != null) {
                    rejectHandler.rejectRequestAfter(_response, e);
                } else {
                    _response.setStatus(properties.getRequestRejctHttpCode());
                }
                this.pushEvent(_request, handler, e.getMessage());
                log.warn(e.getMessage());
                return;
            }
        }
        chain.doFilter(request, response);
    }

    /**
     * 推送拦截事件
     *
     * @param request {@link HttpServletRequest}
     * @param handler {@link HandlerMethod}
     * @param reason  身份验证失败原因
     */
    private void pushEvent(HttpServletRequest request, HandlerMethod handler, String reason) {
        AuditEvent event = new AuditEvent(publisher);
        String ua = request.getHeader(HeaderName.USER_AGENT.getValue());
        UserAgent userAgent = UserAgentParser.parse(ua);
        event.setUserAgent(userAgent);
        event.setClientIp(ServletUtil.getClientIP(request));
        event.setGsid(guidSession.getGuid());
        event.setMethod(handler.getMethod());
        event.setRequestUrl(request.getRequestURL().toString());
        event.setController(handler.getBeanType());
        event.setReason(reason);
        publisher.publishEvent(event);
    }
}
